package org.jsets.fastboot.uid.impl;

import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.commons.lang3.RandomUtils;
import org.jsets.fastboot.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.transaction.annotation.Transactional;
import com.baidu.fsg.uid.utils.DockerUtils;
import com.baidu.fsg.uid.utils.NetUtils;
import com.baidu.fsg.uid.worker.DisposableWorkerIdAssigner;
import com.baidu.fsg.uid.worker.WorkerIdAssigner;
import com.baidu.fsg.uid.worker.WorkerNodeType;
import com.baidu.fsg.uid.worker.entity.WorkerNodeEntity;

/**
 * JDBC WorkerId 提供者
 * 
 * @author wangjie (https://github.com/wj596)
 *
 */
public class JdbcWorkerIdAssigner implements WorkerIdAssigner {

	private static final Logger LOGGER = LoggerFactory.getLogger(DisposableWorkerIdAssigner.class);

	private final JdbcTemplate jdbcTemplate;
	private final String getWorkerNodeByHostPortSql;
	private final String addWorkerNodeSql;

	public JdbcWorkerIdAssigner(JdbcTemplate jdbcTemplate, String schema) {
		this.jdbcTemplate = jdbcTemplate;
		if (StringUtils.isBlank(schema)) {
			getWorkerNodeByHostPortSql = "SELECT ID,HOST_NAME,PORT,TYPE,LAUNCH_DATE,MODIFIED,CREATED FROM SYS_WORKER_NODE WHERE HOST_NAME = ? AND PORT = ?";
			addWorkerNodeSql = "INSERT INTO SYS_WORKER_NODE (HOST_NAME,PORT,TYPE,LAUNCH_DATE,MODIFIED,CREATED)VALUES (?,?,?,?,NOW(),NOW())";
		} else {
			getWorkerNodeByHostPortSql = "SELECT ID,HOST_NAME,PORT,TYPE,LAUNCH_DATE,MODIFIED,CREATED FROM " + schema
					+ ".SYS_WORKER_NODE WHERE HOST_NAME = ? AND PORT = ?";
			addWorkerNodeSql = "INSERT INTO  " + schema
					+ ".SYS_WORKER_NODE (HOST_NAME,PORT,TYPE,LAUNCH_DATE,MODIFIED,CREATED)VALUES (?,?,?,?,NOW(),NOW())";
		}
	}

	/**
	 * Assign worker id base on database.
	 * <p>
	 * If there is host name & port in the environment, we considered that the node
	 * runs in Docker container<br>
	 * Otherwise, the node runs on an actual machine.
	 * 
	 * @return assigned worker id
	 */
	@Transactional
	public long assignWorkerId() {
		// build worker node entity
		WorkerNodeEntity workerNodeEntity = buildWorkerNode();

		// add worker node for new (ignore the same IP + PORT)
		this.addWorkerNode(workerNodeEntity);
		LOGGER.info("Add worker node:" + workerNodeEntity);

		return workerNodeEntity.getId();
	}

	/**
	 * Build worker node entity by IP and PORT
	 */
	private WorkerNodeEntity buildWorkerNode() {
		WorkerNodeEntity workerNodeEntity = new WorkerNodeEntity();
		if (DockerUtils.isDocker()) {
			workerNodeEntity.setType(WorkerNodeType.CONTAINER.value());
			workerNodeEntity.setHostName(DockerUtils.getDockerHost());
			workerNodeEntity.setPort(DockerUtils.getDockerPort());

		} else {
			workerNodeEntity.setType(WorkerNodeType.ACTUAL.value());
			workerNodeEntity.setHostName(NetUtils.getLocalAddress());
			// workerNodeEntity.setPort(System.currentTimeMillis() + "-" +
			// RandomUtils.nextInt(100000));
			// 修改next方法：
			workerNodeEntity.setPort(System.currentTimeMillis() + "-" + RandomUtils.nextInt(0, 100000));
		}

		return workerNodeEntity;
	}

	/**
	 * Get {@link WorkerNodeEntity} by node host
	 * 
	 * @param host
	 * @param port
	 * @return
	 */
	@SuppressWarnings("all")
	private WorkerNodeEntity getWorkerNodeByHostPort(String host, String port) {
		return this.jdbcTemplate.queryForObject(this.getWorkerNodeByHostPortSql, new String[] { host, port },
				new RowMapper<WorkerNodeEntity>() {
					@Override
					public WorkerNodeEntity mapRow(ResultSet rs, int rowNum) throws SQLException {
						WorkerNodeEntity entity = new WorkerNodeEntity();
						entity.setId(rs.getLong("ID"));
						entity.setHostName(rs.getString("HOST_NAME"));
						entity.setPort(rs.getString("PORT"));
						entity.setType(rs.getInt("TYPE"));
						entity.setLaunchDateDate(rs.getDate("LAUNCH_DATE"));
						entity.setModified(rs.getTimestamp("MODIFIED"));
						entity.setCreated(rs.getTime("CREATED"));
						return entity;
					}
				});
	}

	/**
	 * Add {@link WorkerNodeEntity}
	 * 
	 * @param workerNodeEntity
	 */
	public void addWorkerNode(WorkerNodeEntity entity) {
		this.jdbcTemplate.update(this.addWorkerNodeSql, entity.getHostName(), entity.getPort(), entity.getType(),entity.getLaunchDate());
	}

}
